8382226: [lworld] C2: Fix _copyOf/_copyOfRange intrinsic for flat abstract value class arrays#2569
8382226: [lworld] C2: Fix _copyOf/_copyOfRange intrinsic for flat abstract value class arrays#2569chhagedorn wants to merge 11 commits into
Conversation
|
👋 Welcome back chagedorn! A progress list of the required criteria for merging this PR into |
|
@chhagedorn This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 7 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
Webrevs
|
| const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr(); | ||
| const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr(); | ||
| bool exclude_flat = UseArrayFlattening && bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) && | ||
| const bool is_src_abstract_flat_value_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && orig_t->is_flat(); |
There was a problem hiding this comment.
Should we check for !orig_t->is_not_flat() instead?
There was a problem hiding this comment.
That's a good idea to cover all the cases where it could be flat. Updated this and the check for tklass as well accordingly.
| (orig_t == nullptr || (!orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops()))) && | ||
| // Can dest array be flat and contain oops? | ||
| tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); | ||
| can_dest_be_value_class_array && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()))); |
There was a problem hiding this comment.
This should also check for tklass->is_not_flat() instead.
There was a problem hiding this comment.
I agree. I also took another look at the code again and refactored it a bit to make exclude_flat easier to understand. Let me know, what you think.
| tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); | ||
| const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr(); | ||
| const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); | ||
| const bool can_dest_be_value_class_array = dest_klass_t != nullptr && dest_klass_t->can_be_inline_array(); |
There was a problem hiding this comment.
What if dest is not an aryklassptr? I think in that case this should also be true, right?
There was a problem hiding this comment.
See general comment sent above.
| // Can dest array be flat and contain oops? | ||
| tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); | ||
| const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr(); | ||
| const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); |
There was a problem hiding this comment.
This name is a little misleading, I think you want to catch the case orig_t == nullptr below, but if the static type is j.l.O, the runtime type can still be an abstract flat array.
There was a problem hiding this comment.
That's true. Though, I'm wondering if orig_t could even be a non-array pointer at all. The Arrays.copyOf() takes a U[], so after type erasure, we should know it's an Object[] and thus have an array
pointer. It looks like the original code was just defensive by checking orig_t as well. I removed it and testing passed.
chhagedorn
left a comment
There was a problem hiding this comment.
Sorry for taking some more time to respond here. Thanks for your additional questions @merykitty. I've had another closer look at the code and wrote some more tests and discovered more issues:
- When passing in primitive type mirrors like
int.classor forvoid.classtoArrays.copyOf(), there is no klass field and we return null:
valhalla/src/hotspot/share/opto/memnode.cpp
Lines 2834 to 2837 in dc8b7c5
As a result, we crash here becauseklass_nodereturned fromload_klass_from_mirror()istop:
valhalla/src/hotspot/share/opto/library_call.cpp
Line 5195 in dc8b7c5
- Solution: Bail out if
stopped()afterload_klass_from_mirror().
- When passing in an instance class mirror like
A.classwe should throw because it's not an array class. However, with the checks in the current code, we only emit a type array guard which does not bail out when having an instance klass:
valhalla/src/hotspot/share/opto/library_call.cpp
Lines 5196 to 5201 in dc8b7c5
We then try to load the refined array klass from an instance klass pointer in load_default_refined_array_klass() where we read from ObjArrayKlass::next_refined_array_klass_offset() which is garbage.
- Solution: Emit
generate_non_refArray_guard()when the klass pointer is not an array klass.
- We need to check
UseArrayFlatteningand the GC barrier availability after the inserted check for for 2., otherwise, we hit the same problem again when disablingUseArrayFlatteningor not requiring GC barriers at all.
I pushed an update with the following changes:
- Refactored bailout logic again to a separate method where I check all the cases systematically:
- Check dest klass to be an array klass.
- Check all cases for maybe flat and bail out which also covers
j.l.Object. - Check write barriers.
- Added more comments.
- Added a lot more tests by using the Template Framework.
Testing this through t1-4 + stress looked good. Since we now bail out in more cases, I might also run some performance testing.
Let me know, what you think.
| // Can dest array be flat and contain oops? | ||
| tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); | ||
| const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr(); | ||
| const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); |
There was a problem hiding this comment.
That's true. Though, I'm wondering if orig_t could even be a non-array pointer at all. The Arrays.copyOf() takes a U[], so after type erasure, we should know it's an Object[] and thus have an array
pointer. It looks like the original code was just defensive by checking orig_t as well. I removed it and testing passed.
| tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); | ||
| const TypeAryKlassPtr* dest_klass_t = _gvn.type(klass_node)->is_klassptr()->isa_aryklassptr(); | ||
| const bool can_src_be_abstract_flat_value_class_array = orig_t != nullptr && !orig_t->elem()->is_inlinetypeptr() && !orig_t->is_not_flat(); | ||
| const bool can_dest_be_value_class_array = dest_klass_t != nullptr && dest_klass_t->can_be_inline_array(); |
There was a problem hiding this comment.
See general comment sent above.
merykitty
left a comment
There was a problem hiding this comment.
Thanks for taking further look, it looks much better now.
|
Thanks @merykitty for your careful review! |
| // Arrays.copyOf() uses a generic Class parameter which is erased to the raw type Class. This also allows | ||
| // passing in primitive class mirrors like int.class which do not have corresponding Klass* pointers. | ||
| // In these cases, klass_node will be top. Emit a trap to throw in the interpreter in this case. | ||
| bail_out_from_array_copyOf(bailout); |
There was a problem hiding this comment.
What if the j.l.Class is not a constant, but the runtime value is int.class, it will return null, right? Which explains the null check below. In that case, is it an inconsistency? Can it be an issue if a j.l.Class is narrowed to the constant int.class during IGVN, then the node will become top and the whole graph is incorrectly killed.
The provided test cases fail when inlining the
Array.copyOf/copyOfRange()intrinsics where the source array is flat and from an abstract value class.The current code checks whether the source array or the destination array klass contain oops by assuming that a flat value class array is always concrete and thus an
InlineKlass(i.e. can callinline_klass()). However, we could also have abstract value class arrays that are known to be flat (see test cases). This leads to a cast assertion failure because abstract value classes are represented by anInstanceKlassand not anInlineKlass.To fix this, I added a simple bailout when detecting an abstract flat value class array. This is a conservative correctness fix and should be revisited again post-Valhalla-integration. We have JDK-8251971 in place for that which should also tackle other issues around the arraycopy intrinsics and also address performance problems.
Thanks,
Christian
Progress
Issue
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/valhalla.git pull/2569/head:pull/2569$ git checkout pull/2569Update a local copy of the PR:
$ git checkout pull/2569$ git pull https://git.openjdk.org/valhalla.git pull/2569/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 2569View PR using the GUI difftool:
$ git pr show -t 2569Using diff file
Download this PR as a diff file:
https://git.openjdk.org/valhalla/pull/2569.diff
Using Webrev
Link to Webrev Comment